/*******************************************************************************
 * Copyright (c) 2012 March and Conquest.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mrusanov.towers.main.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.WindowConstants;

import mrusanov.towers.cards.AbstractCard;
import mrusanov.towers.configuration.GameConfiguration;
import mrusanov.towers.configuration.GameConfigurationManager;
import mrusanov.towers.configuration.GamefieldConfiguration;
import mrusanov.towers.localization.Localization;
import mrusanov.towers.localization.Localization.LocaleView;
import mrusanov.towers.main.DesktopGame;
import mrusanov.towers.main.GameResult;
import mrusanov.towers.main.ui.stats.StatisticsFrame;
import mrusanov.towers.net.TurnInfo;
import mrusanov.towers.net.TurnInfo.TurnDecision;
import mrusanov.towers.players.Player;

public class MainForm extends JFrame {
	private static final long serialVersionUID = 5363989443805040536L;

	static final Font BOLD_FONT;

	static {
		BOLD_FONT = new Font(null, Font.BOLD, 15);
	}

	private JPanel cardPanel;
	private JPanel gameField;

	private ResourcePanel thisPlayerResourcePanel;
	private TowerPanel thisPlayerTowerPanel;

	private ResourcePanel enemyPlayerResourcePanel;
	private TowerPanel enemyPlayerTowerPanel;

	private final DesktopGame game;

	private final HostDialog hostDialog = new HostDialog();
	private final JoinDialog joinDialog = new JoinDialog();
	private final OptionsDialog optionsDialog = new OptionsDialog();
	private final StatisticsFrame statisticsFrame = new StatisticsFrame();

	private final JMenuBar mainMenu;

	public MainForm(DesktopGame game) {
		this.game = game;
		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

		setTitle(Localization.getString("ui.towerMage"));

		mainMenu = new JMenuBar();
		initMenuComponentsForNewGame();
		initGameField();
		setSize(new Dimension(600, 500));
		setExtendedState(MAXIMIZED_BOTH);

	}

	private void initMenuComponentsForNewGame() {
		mainMenu.removeAll();

		JMenu gameMenu = new JMenu();
		gameMenu.setText(Localization.getString("ui.game"));
		mainMenu.add(gameMenu);

		JMenuItem hostGameMenu = new JMenuItem();
		hostGameMenu.setText(Localization.getString("ui.hostGame"));
		hostGameMenu.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				hostDialog.setLocation(getDialogShowingPoint());
				hostDialog.setVisible(true);
			}
		});

		gameMenu.add(hostGameMenu);

		JMenuItem joinGameMenu = new JMenuItem();
		joinGameMenu.setText(Localization.getString("ui.joinGame"));
		joinGameMenu.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				joinDialog.setLocation(getDialogShowingPoint());
				joinDialog.setVisible(true);
			}
		});

		gameMenu.add(joinGameMenu);

		addOptionsMenuItem(gameMenu);
		addStatisticsMenu(gameMenu);
		addExitMenuItem(gameMenu);

		setJMenuBar(mainMenu);

	}

	private void initGameField() {
		getContentPane().setLayout(new GridLayout(2, 1, 0, 0));

		cardPanel = new JPanel();
		gameField = new JPanel();
		getContentPane().add(gameField);

		getContentPane().add(cardPanel);
		gameField.setLayout(new GridLayout(1, 4, 0, 0));

		thisPlayerResourcePanel = new ResourcePanel();
		thisPlayerResourcePanel.setBorder(BorderFactory.createRaisedBevelBorder());
		thisPlayerTowerPanel = new TowerPanel();
		thisPlayerTowerPanel.setBorder(BorderFactory.createRaisedBevelBorder());

		enemyPlayerTowerPanel = new TowerPanel();
		enemyPlayerTowerPanel.setBorder(BorderFactory.createRaisedBevelBorder());
		enemyPlayerResourcePanel = new ResourcePanel();
		enemyPlayerResourcePanel.setBorder(BorderFactory.createRaisedBevelBorder());

		gameField.add(thisPlayerResourcePanel);
		gameField.add(thisPlayerTowerPanel);
		gameField.add(enemyPlayerTowerPanel);
		gameField.add(enemyPlayerResourcePanel);

		cardPanel.setBorder(BorderFactory.createRaisedBevelBorder());
	}

	private void setCardPanelLayout(GamefieldConfiguration configuration) {
		cardPanel.setLayout(new GridLayout(1, configuration.getCardsNumber(), 20, 0));
	}

	private void addOptionsMenuItem(JMenu menu) {
		JMenuItem item = new JMenuItem(Localization.getString("ui.options"));
		menu.add(item);

		item.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				optionsDialog.setLocation(getDialogShowingPoint());
				optionsDialog.setVisible(true);
			}
		});

	}

	private void addStatisticsMenu(JMenu menu) {
		JMenuItem statistics = new JMenuItem();
		statistics.setText(Localization.getString("ui.stats"));
		menu.add(statistics);

		statistics.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				showStatistics();
			}
		});
	}

	private void addExitMenuItem(JMenu menu) {
		JMenuItem exitGameMenu = new JMenuItem();
		exitGameMenu.setText(Localization.getString("ui.exitGame"));
		menu.add(exitGameMenu);

		exitGameMenu.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
	}

	public void hostingStarted() {
		initMenuComponentsForWaitingConnection();
	}

	public void connectionFailed() {
		JOptionPane.showMessageDialog(this, Localization.getString("ui.connectionFailure"),
				Localization.getString("ui.error"), JOptionPane.ERROR_MESSAGE);
		initMenuComponentsForNewGame();
		refreshGameField();
	}

	private void initMenuComponentsForWaitingConnection() {
		mainMenu.removeAll();

		JMenu gameMenu = new JMenu();
		gameMenu.setText(Localization.getString("ui.game"));
		mainMenu.add(gameMenu);

		JMenuItem stopWaiting = new JMenuItem();
		stopWaiting.setText(Localization.getString("ui.stopWaiting"));
		gameMenu.add(stopWaiting);

		stopWaiting.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				game.stopHosting();
				initMenuComponentsForNewGame();
			}
		});

		addOptionsMenuItem(gameMenu);
		addStatisticsMenu(gameMenu);
		addExitMenuItem(gameMenu);
	}

	private void initMenuComponentsForGameInProgress() {
		mainMenu.removeAll();

		JMenu gameMenu = new JMenu();
		gameMenu.setText(Localization.getString("ui.game"));
		mainMenu.add(gameMenu);

		JMenuItem stopGame = new JMenuItem();
		stopGame.setText(Localization.getString("ui.stopGame"));
		gameMenu.add(stopGame);

		stopGame.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				game.stopGame();
				initMenuComponentsForNewGame();
			}
		});

		addOptionsMenuItem(gameMenu);
		addStatisticsMenu(gameMenu);
		addExitMenuItem(gameMenu);

		refreshGameField();
	}

	private void showStatistics() {
		statisticsFrame.refreshModel();
		statisticsFrame.setLocation(getDialogShowingPoint());
		statisticsFrame.setVisible(true);
	}

	public void hostGame(int port) {
		game.hostGame(port);
	}

	public void hostingError() {
		JOptionPane.showMessageDialog(this, Localization.getString("ui.hostingFailure"),
				Localization.getString("ui.error"), JOptionPane.ERROR_MESSAGE);
		initMenuComponentsForNewGame();
		refreshGameField();
	}

	public void joiningError() {
		JOptionPane.showMessageDialog(this, Localization.getString("ui.connectionFailure"),
				Localization.getString("ui.error"), JOptionPane.ERROR_MESSAGE);
		initMenuComponentsForNewGame();
		refreshGameField();
	}

	public void joinGame(String host, int port) {
		game.joinGame(host, port);
	}

	public void startGame(GamefieldConfiguration configuration) {
		setCardPanelLayout(configuration);
		refreshGameField();
		initMenuComponentsForGameInProgress();
		thisPlayerTowerPanel.setTextToTextField("");
		enemyPlayerTowerPanel.setTextToTextField("");
	}

	private void refreshGameField() {
		refreshTowers();
		refreshResources();
		refreshCardPanel();
	}

	private void refreshCardPanel() {
		cardPanel.removeAll();
		for (AbstractCard card : game.getThisPlayerCards()) {
			CardButton cardComponent = new CardButton(this, card);
			cardPanel.add(cardComponent);
		}

		cardPanel.validate();
		cardPanel.repaint();

	}

	private void refreshTowers() {
		thisPlayerTowerPanel.setTower(game.getThisPlayer().getTower());
		thisPlayerTowerPanel.refreshLabels();

		enemyPlayerTowerPanel.setTower(game.getEnemyPlayer().getTower());
		enemyPlayerTowerPanel.refreshLabels();
	}

	private void refreshResources() {
		thisPlayerResourcePanel.refreshResources(game.getThisPlayer());
		enemyPlayerResourcePanel.refreshResources(game.getEnemyPlayer());
	}

	void currentPlayerPlaysCard(AbstractCard card) {
		TurnInfo info = new TurnInfo(TurnDecision.PLAY_CARD, card);
		game.receiveThisPlayerTurn(info);
		updateGameFieldAfterTurn(info);
	}

	void currentPlayerThrowsCard(AbstractCard card) {
		TurnInfo info = new TurnInfo(TurnDecision.THROW_CARD, card);
		game.receiveThisPlayerTurn(info);
		updateGameFieldAfterTurn(info);
	}

	boolean isCardActive(AbstractCard card) {
		if (game.getGameResult() != GameResult.GAME_IN_PROGRESS) {
			return false;
		}

		if (!game.isThisPlayerTurn()) {
			return false;
		}

		return game.getThisPlayer().getResources().canPayResourceCost(card.getCost());
	}

	private void updateGameFieldAfterTurn(TurnInfo info) {
		refreshGameField();

		Player movedPlayer = game.getPreviouslyMovedPlayer(info);

		if (movedPlayer.equals(game.getEnemyPlayer())) {
			enemyPlayerTowerPanel.addTextToTextField(buildTurnMadeMessage(info, movedPlayer));
			thisPlayerTowerPanel.setTextToTextField("");
		} else {
			thisPlayerTowerPanel.addTextToTextField(buildTurnMadeMessage(info, movedPlayer));
			enemyPlayerTowerPanel.setTextToTextField("");
		}

		if (game.getGameResult() != GameResult.GAME_IN_PROGRESS) {
			showEndGameDialog(game.getGameResult());
			game.restartGameAfterCompletion();
		}
	}

	private void showEndGameDialog(GameResult gameResult) {
		String message = null;
		if (gameResult == GameResult.ENEMY_PLAYER_DEFEATED || gameResult == GameResult.THIS_PLAYER_RAISED_TOWER) {
			message = String.format(Localization.getString("game.result.win"), game.getThisPlayer().getName());
		} else if (gameResult == GameResult.THIS_PLAYER_DEFEATED || gameResult == GameResult.ENEMY_PLAYER_RAISED_TOWER) {
			message = String.format(Localization.getString("game.result.win"), game.getEnemyPlayer().getName());
		} else {
			message = Localization.getString("game.result.bothAreDefeat");
		}

		JDialog dialog = buildEndGameDialog(message);
		dialog.setVisible(true);
	}

	private JDialog buildEndGameDialog(String message) {
		final JDialog dialog = new JDialog(this, Localization.getString("ui.gameOverTitle"), true);
		JPanel messagePane = new JPanel();
		messagePane.add(new JLabel(message));
		dialog.getContentPane().add(messagePane);
		JPanel buttonPane = new JPanel();
		JButton button = new JButton(Localization.getString("ui.ok"));
		buttonPane.add(button);
		button.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				dialog.setVisible(false);
			}
		});

		dialog.getContentPane().add(buttonPane, BorderLayout.SOUTH);
		dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		dialog.pack();
		dialog.setLocation(getDialogShowingPoint());

		return dialog;
	}

	private Point getDialogShowingPoint() {
		return new Point(getWidth() / 3, getHeight() / 3);
	}

	private String buildTurnMadeMessage(TurnInfo turnInfo, Player player) {
		if (turnInfo.getTurnDecision() == TurnDecision.PLAY_CARD) {
			return String.format(Localization.getString("game.anotherPlayerPlayedCard"), player.getName(),
					Localization.getString(turnInfo.getCard().getNameMessageKey()))
					+ "\n" + Localization.getString(turnInfo.getCard().getDescriptionMessageKey());
		} else {
			return String.format(Localization.getString("game.anotherPlayerThrownCard"), player.getName());
		}
	}

	public boolean isCurrentPlayerMove() {
		return game.isThisPlayerTurn();
	}

	public void unknownError(Exception ex) {
		JOptionPane.showMessageDialog(this, ex.getLocalizedMessage(), Localization.getString("ui.error"),
				JOptionPane.ERROR_MESSAGE);
		initMenuComponentsForNewGame();
	}

	public class UpdateUIAfterMove implements UITask {

		private final TurnInfo info;

		public UpdateUIAfterMove(TurnInfo info) {
			this.info = info;
		}

		@Override
		public void executeTask() {
			updateGameFieldAfterTurn(info);
		}
	}

	private class HostDialog extends JFrame {
		private static final long serialVersionUID = -9157536886737732544L;

		private JLabel enterPort;
		private JSpinner portSpinner = new JSpinner();

		public HostDialog() {
			enterPort = new JLabel(Localization.getString("ui.port"));
			portSpinner.getModel().setValue(GameConfigurationManager.getConfiguration().getRememberedHostingPort());

			setLayout(new GridLayout(2, 2, 20, 20));
			getContentPane().add(enterPort);
			getContentPane().add(portSpinner);

			JButton okButton = new JButton(Localization.getString("ui.ok"));
			JButton cancelButton = new JButton(Localization.getString("ui.cancel"));

			getContentPane().add(okButton);
			getContentPane().add(cancelButton);

			okButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					GameConfiguration conf = GameConfigurationManager.getConfiguration();
					conf.setRememberedHostingPort((Integer) portSpinner.getValue());
					GameConfigurationManager.saveConfiguration(conf);

					hostGame((Integer) portSpinner.getValue());
					setVisible(false);
				}
			});

			cancelButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					setVisible(false);
				}
			});

			setTitle(Localization.getString("ui.hostGame"));
			pack();
		}
	}

	private class JoinDialog extends JFrame {

		private static final long serialVersionUID = -8970318978908929060L;

		private JLabel enterPort;
		private JSpinner portSpinner = new JSpinner();
		private JLabel enterHost;
		private JTextField hostField;

		public JoinDialog() {
			enterPort = new JLabel(Localization.getString("ui.port"));
			portSpinner.getModel().setValue(GameConfigurationManager.getConfiguration().getRememberedJoiningPort());

			enterHost = new JLabel(Localization.getString("ui.host"));
			hostField = new JTextField(GameConfigurationManager.getConfiguration().getRememberedHost());

			JButton okButton = new JButton(Localization.getString("ui.ok"));
			JButton cancelButton = new JButton(Localization.getString("ui.cancel"));

			okButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					int port = (Integer) portSpinner.getValue();
					String host = hostField.getText();
					GameConfiguration conf = GameConfigurationManager.getConfiguration();
					conf.setRememberedHost(host);
					conf.setRememberedJoiningPort(port);
					GameConfigurationManager.saveConfiguration(conf);
					joinGame(host, port);
					setVisible(false);
				}
			});

			cancelButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					setVisible(false);
				}
			});

			setLayout(new GridLayout(3, 2, 20, 20));

			add(enterHost);
			add(hostField);

			add(enterPort);
			add(portSpinner);

			add(okButton);
			add(cancelButton);

			setTitle(Localization.getString("ui.joinGame"));
			pack();
		}
	}

	private class OptionsDialog extends JFrame {
		private static final int MAX = 1000;
		private static final int MIN_ONE = 1;
		private static final int MIN_ZERO = 1;
		private static final int STEP = 1;
		private static final int VALUE = MIN_ONE;

		private static final long serialVersionUID = -4820309926443132409L;

		private JLabel languageLabel = new JLabel(Localization.getString("ui.language"));
		private JLabel nameLabel = new JLabel(Localization.getString("ui.name"));
		private JLabel wallHeightLabel = new JLabel(Localization.getString("ui.initialWallHeight"));
		private JLabel towerHeightLabel = new JLabel(Localization.getString("ui.initialTowerHeight"));
		private JLabel incomeLabel = new JLabel(Localization.getString("ui.initialIncome"));
		private JLabel resourceLabel = new JLabel(Localization.getString("ui.initialResources"));
		private JLabel cardsLabel = new JLabel(Localization.getString("ui.cardsNumber"));
		private JLabel towerHeightToWinLabel = new JLabel(Localization.getString("ui.towerHeightToWin"));
		private JLabel soundsLabel = new JLabel(Localization.getString("ui.sounds"));

		private JTextField nameField = new JTextField();
		private JComboBox languageComboBox = new JComboBox();
		private JSpinner wallHeightSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ZERO, MAX, STEP));
		private JSpinner towerHeightSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ONE, MAX, STEP));
		private JSpinner incomeSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ONE, MAX, STEP));
		private JSpinner resourceSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ZERO, MAX, STEP));
		private JSpinner cardsSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ONE, MAX, STEP));
		private JSpinner towerHeightToWinSpinner = new JSpinner(new SpinnerNumberModel(VALUE, MIN_ONE, MAX, STEP));
		private JCheckBox soundsCheckBox = new JCheckBox();

		public OptionsDialog() {
			GameConfiguration configuration = GameConfigurationManager.getConfiguration();

			nameField.setText(configuration.getPlayerName());
			DefaultComboBoxModel model = new DefaultComboBoxModel(Localization.LOCALES);
			model.setSelectedItem(Localization.getLang());
			languageComboBox.setModel(model);

			GamefieldConfiguration gameField = configuration.getGamefieldConfiguration();

			wallHeightSpinner.getModel().setValue(gameField.getInitialWall());
			towerHeightSpinner.getModel().setValue(gameField.getInitialTower());
			incomeSpinner.getModel().setValue(gameField.getInitialIncome());
			resourceSpinner.getModel().setValue(gameField.getInitialResources());
			cardsSpinner.getModel().setValue(gameField.getCardsNumber());
			towerHeightToWinSpinner.getModel().setValue(gameField.getTowerHeightToWin());
			soundsCheckBox.setSelected(configuration.isSoundsEnabled());

			setLayout(new GridLayout(10, 2, 20, 20));

			add(nameLabel);
			add(nameField);
			add(languageLabel);
			add(languageComboBox);
			add(towerHeightLabel);
			add(towerHeightSpinner);
			add(wallHeightLabel);
			add(wallHeightSpinner);
			add(incomeLabel);
			add(incomeSpinner);
			add(resourceLabel);
			add(resourceSpinner);
			add(cardsLabel);
			add(cardsSpinner);
			add(towerHeightToWinLabel);
			add(towerHeightToWinSpinner);
			add(soundsLabel);
			add(soundsCheckBox);

			JButton okButton = new JButton(Localization.getString("ui.ok"));
			add(okButton);

			JButton cancelButton = new JButton(Localization.getString("ui.cancel"));
			add(cancelButton);

			cancelButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					setVisible(false);
				}
			});

			okButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					GameConfiguration configuration = new GameConfiguration();
					LocaleView localeView = (LocaleView) languageComboBox.getModel().getSelectedItem();

					configuration.setLanguage(localeView.getLocale());
					configuration.setPlayerName(nameField.getText());
					game.setCurrentPlayerName(nameField.getText());

					GamefieldConfiguration gamefieldConfiguration = new GamefieldConfiguration();
					gamefieldConfiguration.setInitialIncome((Integer) incomeSpinner.getValue());
					gamefieldConfiguration.setInitialResources((Integer) resourceSpinner.getValue());
					gamefieldConfiguration.setInitialTower((Integer) towerHeightSpinner.getValue());
					gamefieldConfiguration.setInitialWall((Integer) wallHeightSpinner.getValue());
					gamefieldConfiguration.setTowerHeightToWin((Integer) towerHeightToWinSpinner.getValue());
					gamefieldConfiguration.setCardsNumber((Integer) cardsSpinner.getValue());

					configuration.setGamefieldConfiguration(gamefieldConfiguration);
					configuration.setSoundsEnabled(soundsCheckBox.isSelected());

					GameConfigurationManager.saveConfiguration(configuration);
					setVisible(false);

					if (!localeView.getLocale().equals(Localization.getLang().getLocale())) {
						JOptionPane.showMessageDialog(MainForm.this,
								Localization.getString("ui.restartGameToApplyLanguageChange"),
								Localization.getString("ui.options"), JOptionPane.INFORMATION_MESSAGE);
					}
				}
			});

			setTitle(Localization.getString("ui.options"));
			pack();
		}
	}

}
